package com.hxk.juc;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * 由于juc下的java.util.concurrent.atomic包下的所有原子类底层都是使用CAS原理实现的，所以会有ABA问题的存在
 *   那么什么是ABA问题?
 *      答：所谓ABA问题，就是比较并交换的循环，存在一个【时间差】，而这个时间差可能带来意想不到的问题。
 *          比如线程T1将值从A改为B，然后又从B改为A。线程T2看到的就是A，但是【却不知道这个A发生了更改】。
 *              尽管线程T2 CAS操作成功，但不代表就没有问题。
 *   本Case是对ABA问题的一个代码演示和对ABA问题处理代码演示的案例
 *      那么怎么处理ABA问题：
 *          解决思路，变量+使用版本号的处理方案来解决
 *      具体解决方案：
 *          使用juc提供的`AtomicStampedReference`类可以解决ABA问题。这个类维护了一个“【版本号】”Stamp，在进行CAS操作的时候，不仅要比较当前值，还要比较【版本号】。
 *          只有两者都相等，才执行更新操作。
        使用方式如下：
        AtomicStampedReference.compareAndSet(expectedReference,newReference,oldStamp,newStamp);
 */
public class ABACase {

    public static void main(String[] args) {
        //produceABA();
        solveABA();
    }

    /**
     * 解决ABA问题方案
     * 通过该方法能够解决produceABA带来的ABA问题
     *      还是那句话：
     *      如果在只注重结果不注重过程的业务场景中，可以不用考虑该问题
     *      但是要在注重业务过程的场景中，就要考虑ABA问题了
     */
    public static void solveABA(){
        //有两个值initialRef为实际值，initialStamp为版本号
        AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(5,1);
        System.out.println(Thread.currentThread().getName()+"\t ABA操作的A后，值："+asr.getReference()+", 版本号；"+asr.getStamp());
        new Thread(()->{
            int stamp = asr.getStamp();
            int val = asr.getReference();
            System.out.println(Thread.currentThread().getName()+"\t 值："+val+",版本号："+stamp);
            //此处休息1秒钟是为了让下面的B线程也同样获取和我一样的值和版本号
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            asr.compareAndSet(5,100,asr.getStamp(),asr.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t ABA操作的B后，值："+asr.getReference()+", 版本号；"+asr.getStamp());
            asr.compareAndSet(100,5,asr.getStamp(),asr.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t ABA操作的A后，值："+asr.getReference()+", 版本号；"+asr.getStamp());
        },"A").start();

        new Thread(()->{
            int stamp = asr.getStamp();
            int val = asr.getReference();
            System.out.println(Thread.currentThread().getName()+"\t 值："+val+",版本号："+stamp);
            //此处休息3秒钟是为了让上面的A线程完成一次ABA操作
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean flag = asr.compareAndSet(5, 2020, stamp, stamp+1);
            System.out.println(Thread.currentThread().getName()+"\t 操作是否成功："+flag+",当前最新值："+asr.getReference()+", 版本号；"+asr.getStamp());
        },"B").start();
    }

    /**
     * 产生ABA问题方法;
     * 该问题未必就是问题，要根据业务场景来定
     *  如果在只注重结果不注重过程的业务场景中，可以不用考虑该问题
     *  但是要在注重业务过程的场景中，就要考虑ABA问题了
     */
    public static void produceABA(){
        AtomicInteger atomicInteger = new AtomicInteger(5);// A
        System.out.println(Thread.currentThread().getName()+"\t ABA中的A操作后值："+atomicInteger.get());
        new Thread(()->{
            atomicInteger.compareAndSet(5, 100); //B
            System.out.println(Thread.currentThread().getName()+"\t ABA中的B操作后值："+atomicInteger.get());
            atomicInteger.compareAndSet(100,5); // A
            System.out.println(Thread.currentThread().getName()+"\t ABA中的A操作后值："+atomicInteger.get());
        },"T1").start();

        new Thread(()->{
            System.out.println();
            //让当前线程休息1秒钟，确保上面的T1线程完成一次ABA操作
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicInteger.compareAndSet(5,2020);
            System.out.println(Thread.currentThread().getName()+"\t 值："+atomicInteger.get());
        },"T2").start();
    }
}
